﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Compression;
using System.IO;
using System.Web.Script.Serialization;
using System.Diagnostics;
//using Microsoft.Build.BuildEngine;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using System.Windows.Forms;

namespace MainScript
{
    class Program
    {
        static ConsoleLogger log = new ConsoleLogger(LogType.Warning);
        private static Person current;
        private static string checker_path;

        static string SelectFile(bool zip)
        {
            OpenFileDialog openFileDialog1 = new OpenFileDialog();

            // Set filter options and filter index.
            openFileDialog1.Filter = (zip ? "Zip Files (*.zip)|*.zip" : "Executable file (*.exe)|*.exe") + "|All Files (*.*)|*.*";
            openFileDialog1.FilterIndex = 1;

            openFileDialog1.Multiselect = false;

            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                return openFileDialog1.FileName;
            }
            return "####";
        }

        static void CopyHere(string fname)
        {
            var dest = String.Format("{0}\\{1}", Environment.CurrentDirectory, new FileInfo(fname).Name);
            if (dest != fname)
            {
                File.Copy(fname, dest, true);
                Console.WriteLine(String.Format("Copied file {0} to working directory...", fname));
            }
        }

        [STAThread]
        static void Main(string[] args)
        {
            var ReportContent = new List<string>(new string[] { "Name|Mark|Message|Name match|Expected Name|Found Name" });

            Console.WriteLine("Select downloaded path");
            var base_file = SelectFile(true);

            if (base_file == "####")
            {
                log.ReportError("Aborted by the user...\nPress a key to exit");
                Console.ReadKey();
                return;
            }

            CopyHere(base_file);

            Console.WriteLine("Select checker path");
            checker_path = SelectFile(false);

            Console.WriteLine(String.Format("{0}\\{1}", Environment.CurrentDirectory, new FileInfo(checker_path).Name));

            if (checker_path == "####")
            {
                log.ReportError("Aborted by the user...\nPress a key to exit");
                Console.ReadKey();
                return;
            }

            CopyHere(checker_path);

            SanitizeFolder(Path.GetFileNameWithoutExtension(base_file));
            ExtractZipFile(base_file, true);

            var base_dir = Path.Combine(Environment.CurrentDirectory, Path.GetFileNameWithoutExtension(base_file));

            //var base_dir = Environment.CurrentDirectory;
            Environment.CurrentDirectory = base_dir;
            var files = Directory.GetFiles(Environment.CurrentDirectory).OrderBy(s => s);
            var students = new List<Person>();
            //var person = (from d in dirs select new Person(d)).ToArray();

            foreach (var file in files)
            {
                var name = Path.GetFileName(file).Split("_".ToCharArray())[0];
                var nname = name + ".zip";
                try
                {
                    File.Move(Path.GetFileName(file), nname);
                    SanitizeFolder(Path.GetFileNameWithoutExtension(nname));
                    ExtractZipFile(nname, true);
                    var person = ProcessFolder(name);
                    students.Add(person);
                }
                catch (Exception e)
                {
                    log.ReportError(string.Format("Error: user {0}\nwith full path {1}\nfailed with\n\t{2}", name, nname, e.Message));
                    CompleteMessages();
                }

                Environment.CurrentDirectory = base_dir;
                #region old
                //log.ReportDirectory("Moving to {0}", dir);
                //Environment.CurrentDirectory = dir;
                //var files = Directory.GetFiles(dir);
                //var student = new Person(dir);
                //students.Add(student);
                //if (files.Length != 1)
                //{
                //    log.ReportError("Directory {0} contains more than 1 file", dir);
                //    ReportError(student, Error.ErrorInZipFile);
                //    continue;
                //}
                //var file = files[0];
                ////if (Path.GetExtension(file) != "zip")
                ////{
                ////    Console.WriteLine(String.Format("File {0} is not a zip file", file));
                ////    ReportError(student, Error.ErrorInZipFile);
                ////    continue;
                ////}
                //try
                //{
                //    ZipFile.ExtractToDirectory(file, Path.GetFileNameWithoutExtension(file));
                //    student.Message = "OK";
                //    student.Mark = 3;
                //    Console.WriteLine("Done---------------------------------------------------------------");
                //    var folder = FindWithExtension(Environment.CurrentDirectory, ".csproj");
                //    Console.WriteLine(folder);
                //    //Console.ReadKey();
                //    MsBuild(folder);
                //    Console.ReadKey();
                //}
                //catch (Exception e)
                //{
                //    log.ReportError("Error in decompression in File {0} due {1}", file, e.Message);
                //    ReportError(student, Error.ErrorInZipFile);
                //    continue;
                //}
                #endregion
            }

#warning a capo su correttore

            Environment.CurrentDirectory = base_dir;
            var json = ToJson(students.Select(p => p.ToSinteticPerson()));
            System.Windows.Forms.Clipboard.SetText(json);
            File.WriteAllText("..\\res.json", json, Encoding.Unicode);
            ReportContent.AddRange(students.Select(s => s.ToString()));
            File.WriteAllLines("..\\report.csv", ReportContent.ToArray(), Encoding.Unicode);
            log.SaveLogs("..\\console_logs.txt");
            log.ReportWin("Done {0} students. Press a key to exit", files.Count());
            Console.ReadKey();
        }

        static Person ProcessFolder(string d_name)
        {
            var person = new Person(d_name);
            current = person;
            //if (!person.Surname.ToLower().StartsWith("l") && !person.Name.ToLower().StartsWith("l"))
            //{
            //    Console.WriteLine("skipped");
            //    return person;
            //}
            log.ReportStatus("Processing person {0}", person.CorrectName);
            log.Indent++;
            Environment.CurrentDirectory = d_name;
            
            log.ReportDirectory("Entered to folder {0}", d_name);

            #region old
            /*var matching_files = Directory.GetFiles(Environment.CurrentDirectory, "*.zip", SearchOption.TopDirectoryOnly);
            if (matching_files.Length != 1)
            {
                log.ReportError("Error on student {0} {1}. Not just one zip file...", person.Surname, person.Name);
                if (matching_files.Length == 0)
                {
                    ReportError(person, Errors.WrongZipFile);
                }
                else
                {
                    throw new Exception("Error in files");
                }
                CompleteMessages(person);
                return person;
            }

            var zip_file = ;

            SanitizeFolder(Path.GetFileNameWithoutExtension(zip_file));

            var unzip_successfully = ExtractZipFile(zip_file);

            if (!unzip_successfully)
            {
                ReportError(person, Errors.UnzipError);
                CompleteMessages(person);
                return person;
            }*/
            #endregion

            var out_folder = CompileProjAndGetOutFolder();

            if (out_folder == null)
            {
                ReportError(person, Errors.CompileError);
                CompleteMessages(person);
                return person;
            }

            Environment.CurrentDirectory = String.Format("{0}\\{1}", Environment.CurrentDirectory, out_folder);
            log.ReportDirectory("Moved to {0}", Environment.CurrentDirectory);

            if (!ExecuteProject())
            {
                ReportError(person, Errors.MissingCorrettore);
            }

            CompleteMessages(person);

            return person;
        }

        private static void CompleteMessages()
        {
            log.Indent--;
            log.ReportStatus("Person completed.");
            log.ReportStatus("");
        }

        private static void CompleteMessages(Person person)
        {
            log.Indent--;
            log.ReportStatus("Person {0} completed.", person.CorrectName);
            log.ReportStatus("");
        }

        private static bool ExecuteProject()
        {
            var checker_name = Environment.CurrentDirectory + "\\MyCorrettore.exe";
            File.Copy(checker_path, checker_name);
            System.Diagnostics.Process process = new System.Diagnostics.Process();
            System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
            startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            startInfo.FileName = checker_name; //Environment.CurrentDirectory + "\\Correttore.exe";
            startInfo.Arguments = "quiet";
            process.StartInfo = startInfo;
            RedirectOutput(process);
            try
            {
                process.Start();
                process.BeginOutputReadLine();
                process.WaitForExit();
            }
            catch (Exception e)
            {
                log.ReportError("Error launching {0} {1}", startInfo.FileName, e.Message);
                return false;
            }
            return true;
        }

        static string CompileProjAndGetOutFolder()
        {
            var src_files = new string[] { Directory.GetFiles(Environment.CurrentDirectory, "*.fs", SearchOption.AllDirectories).OrderBy(s => s.Length).First() };

            var matches = Grepper.Grepper.DefaultForbidden(src_files[0]);
            if (matches.Count > 0)
            {
                foreach (var match in matches)
                {
                    log.ReportSmartass(string.Format("Smartass... found {0} at row {1}", match.Item1, match.Item2));
                }
            }

            var matching_projs = Directory.GetFiles(Environment.CurrentDirectory, "*.fsproj", SearchOption.AllDirectories);
            if (matching_projs.Length < 1)
            {
                log.ReportError("Not just one solution file...");
                return null;
            }

            if (matching_projs.Length > 1)
            {
                log.ReportWarn("Found {0} .fsproj files. Using the shortest.", matching_projs.Length);
                matching_projs = new string[] { matching_projs.OrderBy(s => s.Length).First() };
            }

            var proj = matching_projs[0];
            Environment.CurrentDirectory = new FileInfo(proj).Directory.FullName;
            log.ReportDirectory("Moved to directory {0}", Environment.CurrentDirectory);

            SanitizeFolder("bin");

            var output_folder = MsBuild(proj);

            return output_folder;
        }
        
        static void SanitizeFolder(string destination_name)
        {
            var nested_dirs = Directory.GetDirectories(Environment.CurrentDirectory).Select(s => new DirectoryInfo(s).Name);
            if (nested_dirs.Contains(destination_name))
            {
                log.ReportDirectory("Directory {0} already exists. Delete (Y/N)", destination_name);
                if (true || Console.ReadLine().ToLower() == "y")
                {
                    var redo = false;
                    do
                    {
                        try
                        {
                            redo = false;
                            Directory.Delete(destination_name, true);
                            log.ReportDirectory("Directory {0} deleted succesfully", destination_name);
                        }
                        catch (Exception)
                        {
                            log.ReportError("Can't delete {0}. Close all documents and explorer. Continue? (Y/N)", destination_name);
                            redo = Console.ReadLine().ToLower() != "n";
                        } 
                    } while (redo);
                }
            }
        }

        static bool ExtractZipFile(string f_name, bool to_curr_dir = false)
        {
            try
            {
                ZipFile.ExtractToDirectory(f_name, to_curr_dir ? Path.Combine(Environment.CurrentDirectory, Path.GetFileNameWithoutExtension(f_name)) : Path.GetFileNameWithoutExtension(f_name));
            }
            catch (Exception e)
            {
                log.ReportError("Error in decompression of File {0} due {1}", f_name, e.Message);
                return false;
            }

            log.ReportWin("{0} extracted succesfully...", f_name);
            return true;
        }

        static void EnterToSolutionDirectory()
        {
            var sln_path = FindWithExtension(Environment.CurrentDirectory, ".sln");
            var directory = new FileInfo(sln_path).DirectoryName;
            Environment.CurrentDirectory = directory;
        }

        static void ReportError(Person person, string message)
        {
            person.Mark = 0;
            person.Message = message;
        }

        static string ToJson<T>(IEnumerable<T> persons)
        {
            var jss = new JavaScriptSerializer();
            return jss.Serialize(persons);
        }

        static string FindWithExtension(string base_path, string extension)
        {
            return Directory.GetFiles(base_path, "*"+extension, SearchOption.AllDirectories)[0];
            //if (Directory.GetFiles(base_path).Any(f => Path.GetExtension(f) == extension))
            //{
            //    var file = Directory.GetFiles(base_path).Where(f => Path.GetExtension(f) == extension).First();
            //    Console.WriteLine(file);
            //    return file;
            //}
            //foreach (var d in Directory.GetDirectories(base_path))
            //{
            //    var rec = FindWithExtension(d, extension);
            //    if (rec != "")
            //        return rec;
            //}
            //return "";
        }

        static void RedirectOutput(Process myProcess)
        {
            // This needs to be set to false, in order to actually redirect the standard shell output
            myProcess.StartInfo.UseShellExecute = false;
            myProcess.StartInfo.RedirectStandardOutput = true;

            // This is the event that is triggered when output data is received.
            // I changed this to Console.WriteLine() - you can use whatever you want basically...
            myProcess.OutputDataReceived += myProcess_OutputDataReceived; //(sender, args) => Console.WriteLine(args.Data);

            //myProcess.Start();

            //myProcess.BeginOutputReadLine(); // without this, the OutputDataReceived event won't ever be triggered
        }

        static void myProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (e.Data == null)
            {
                return;
            }
            var s = e.Data;
            if (e.Data.Length < 3)
            {
                Console.WriteLine(e.Data);
                return;
            }
            string student_name = "";
            if (s.StartsWith("Studente "))
            {
                student_name = s.Substring(8);
                if (!current.IsSame(student_name))
                {
                    log.ReportWarn("Mismatch on names. Expected {0}; found {1}", current.CorrectName, student_name);
                }
                else
                {
                    log.ReportWin("Student name {0} match", current.CorrectName);
                }
                log.ReportStatus(s);
            }
            else
            {
                var offset = s.IndexOf(':') + 1;
                var message = s.Substring(offset + 1);
                current.Message += s + Environment.NewLine;
                if (message.StartsWith("Corretta"))
                {
                    current.Mark++;
                    log.ReportWin(s);
                }
                else if (message.StartsWith("Loop infinito"))
                {
                    log.ReportError(s);
                }
                else if (message.StartsWith("Sbagliata"))
                {
                    log.ReportError(s);
                }
                else if (message.StartsWith("Eccezione"))
                {
                    log.ReportWarn(s);
                }
                else
                {
                    log.ReportStatus(s);
                }
            }
        }

        static string MsBuild(string fileName)
        {
            var logger = new Microsoft.Build.Logging.FileLogger();//ConsoleLogger(LoggerVerbosity.Normal);
            Project project = new Project(fileName);
            var done = project.Build(logger);
            if (!done)
            {
                log.ReportError("Error compiling {0}. See msbuild.log for detail...", fileName);
                return null;
            }
            
            var output_path = project.Properties.First(p => p.Name == "OutputPath").EvaluatedValue;
            log.ReportWin("Compilation SUCCEDED. Output in {0}. See msbuild.log for detail", output_path);
            return output_path;
        }

        #region startProcess
        //System.Diagnostics.Process process = new System.Diagnostics.Process();
        //System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
        //startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
        //startInfo.FileName = Environment.SystemDirectory;
        //startInfo.Arguments = String.Format("/clp:ErrorsOnly {0}", fileName);
        //process.StartInfo = startInfo;
        //RedirectOutput(process);
        //process.Start();
        //process.BeginOutputReadLine();
        #endregion

    }
}
